﻿using System;

namespace JiShiBen.Core.DataStructure
{
	public class FragmentList
	{
		public long Length { get; private set; }

		public FragmentListItem First { get; set; }

		public bool IsEmpty { get { return First == null; } }

		public void Insert(Fragment fragment, long offset = 0)
		{
			var listItem = new FragmentListItem(fragment);

			if (IsEmpty)
			{
				First = listItem;
				Length = listItem.Length;
			}
			else
			{
				if (offset < 0)
					throw new ArgumentOutOfRangeException("offset", offset, "offset must be positive integral number.");

				long adjustedOffset;
				var temp = GetListItem(offset, out adjustedOffset);
				if (temp == null)
					throw new ArgumentOutOfRangeException("offset", offset, "value specified exceed the end of list.");

				AdjustListItem(temp, adjustedOffset, listItem);

				Length += listItem.Length;
			}
		}

		private void AdjustListItem(FragmentListItem target, long adjustedOffset, FragmentListItem newItem)
		{
			if (adjustedOffset > target.Offset)
			{
				var prefix = new FragmentListItem(new Fragment(target.HostIndex, target.Offset, adjustedOffset), target.Previous, newItem);
				if (target != First)
					target.Previous.Next = prefix;
				newItem.Previous = prefix;
			}
			else
			{
				if (target != First)
					target.Previous.Next = newItem;
				newItem.Previous = target.Previous;
			}

			if (adjustedOffset < target.Length)
			{
				var suffix = new FragmentListItem(new Fragment(target.HostIndex, adjustedOffset, target.Length - adjustedOffset), newItem,
															   target.Next);
				if (target.Next != null)
					target.Next.Previous = suffix;
				newItem.Next = suffix;
			}
			else
			{
				if (target.Next != null)
					target.Next.Previous = newItem;
				newItem.Next = target.Next;
			}
		}

		public void Remove(long offset, long length)
		{
			if (IsEmpty)
				return;

			if (offset < 0)
				throw new ArgumentOutOfRangeException("offset", offset, "offset must be positive integral number.");
			if (offset + length > Length)
				throw new ArgumentOutOfRangeException("length", length, "Value specified exceed the end of list.");

			long adjustedOffset;
			var listItem = GetListItem(offset, out adjustedOffset);
			if (listItem == null)
				throw new ArgumentOutOfRangeException("offset", offset, "Value specified exceed the end of list.");

			var remaining = length;
			while (remaining > 0 && listItem != null)
			{
				var len = listItem.Length + listItem.Offset;
				if (adjustedOffset + remaining > len)
				{
					remaining -= len - adjustedOffset;
					listItem.SetLength(listItem.Length - (len - adjustedOffset));
					if (listItem.Length == 0)
					{
						listItem.Previous.Next = listItem.Next;
						listItem.Next.Previous = listItem.Previous;
					}
				}
				else if (adjustedOffset == listItem.Offset)
				{
					listItem.SetOffset(listItem.Offset + remaining);
					listItem.SetLength(listItem.Length - remaining);
					remaining = 0;
				}
				else
				{
					var suffix =
						new FragmentListItem(new Fragment(listItem.HostIndex, adjustedOffset + remaining,
						                                  (listItem.Length + listItem.Offset) - (adjustedOffset + remaining)));
					var prefix = listItem;
					prefix.SetLength(adjustedOffset - prefix.Offset);

					prefix.Previous = listItem.Previous;
					if (prefix.Previous != null)
						prefix.Previous.Next = prefix;
					prefix.Next = suffix;
					suffix.Previous = prefix;
					suffix.Next = listItem.Next;
					if (suffix.Next != null)
						suffix.Next.Previous = suffix;

					if (prefix.Length == 0 && prefix.Previous != null)
					{
						prefix.Previous.Next = suffix;
						suffix.Previous = prefix.Previous;
					}

					if (suffix.Length == 0 && suffix.Next != null)
					{
						suffix.Previous.Next = suffix.Next;
						suffix.Next.Previous = suffix.Previous;
					}

					remaining = 0;
				}

				listItem = listItem.Next;
				if (listItem != null)
					adjustedOffset = listItem.Offset;
			}
		}

		public FragmentListItem GetListItem(long offset, out long adjustedOffset)
		{
			var temp = First;
			long counted = 0;
			adjustedOffset = 0;

			while (temp != null)
			{
				if (offset < (counted + temp.Length))
				{
					adjustedOffset = (offset - counted) + temp.Offset;
					break;
				}
				counted += temp.Length;
				temp = temp.Next;
			}

			return temp;
		}

		#region Sub-type

		public class FragmentListItem
		{
			public FragmentListItem(Fragment fragment)
				: this(fragment, null, null)
			{
			}

			public FragmentListItem(Fragment fragment, FragmentListItem prev, FragmentListItem next)
			{
				Value = fragment;
				Previous = prev;
				Next = next;
			}

			public long Length
			{
				get { return Value.Length; }
			}

			public long Offset
			{
				get { return Value.Offset; }
			}

			public HostType HostIndex
			{
				get { return Value.HostIndex; }
			}

			public void SetOffset(long offset)
			{
				Value.SetOffset(offset);
			}

			public void SetLength(long length)
			{
				Value.SetLength(length);
			}

			public long LastIndex
			{
				get { return Value.LastIndex; }
			}

			public FragmentListItem Previous { get; set; }
			private Fragment Value { get; set; }
			public FragmentListItem Next { get; set; }

			public static implicit operator Fragment(FragmentListItem item)
			{
				return item.Value;
			}
		}

		#endregion
	}
}
